#!/usr/bin/python
#
# Copyright (c) Ansible project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

DOCUMENTATION = r"""
module: xcc_redfish_command
short_description: Manages Lenovo Out-Of-Band controllers using Redfish APIs
version_added: 2.4.0
description:
  - Builds Redfish URIs locally and sends them to remote OOB controllers to perform an action or get information back or update
    a configuration attribute.
  - Manages virtual media.
  - Supports getting information back using GET method.
  - Supports updating a configuration attribute using PATCH method.
  - Supports performing an action using POST method.
extends_documentation_fragment:
  - community.general.attributes
  - community.general.redfish
attributes:
  check_mode:
    support: none
  diff_mode:
    support: none
options:
  category:
    required: true
    description:
      - Category to execute on OOB controller.
    type: str
  command:
    required: true
    description:
      - List of commands to execute on OOB controller.
    type: list
    elements: str
  baseuri:
    required: true
    description:
      - Base URI of OOB controller.
    type: str
  username:
    description:
      - Username for authentication with OOB controller.
    type: str
  password:
    description:
      - Password for authentication with OOB controller.
    type: str
  auth_token:
    description:
      - Security token for authentication with OOB controller.
    type: str
  timeout:
    description:
      - Timeout in seconds for URL requests to OOB controller.
    default: 10
    type: int
  resource_id:
    description:
      - The ID of the System, Manager or Chassis to modify.
    type: str
  virtual_media:
    description:
      - The options for VirtualMedia commands.
    type: dict
    suboptions:
      media_types:
        description:
          - The list of media types appropriate for the image.
        type: list
        elements: str
        default: []
      image_url:
        description:
          - The URL of the image to insert or eject.
        type: str
      inserted:
        description:
          - Indicates if the image is treated as inserted on command completion.
        type: bool
        default: true
      write_protected:
        description:
          - Indicates if the media is treated as write-protected.
        type: bool
        default: true
      username:
        description:
          - The username for accessing the image URL.
        type: str
      password:
        description:
          - The password for accessing the image URL.
        type: str
      transfer_protocol_type:
        description:
          - The network protocol to use with the image.
        type: str
      transfer_method:
        description:
          - The transfer method to use with the image.
        type: str
  resource_uri:
    description:
      - The resource URI to get or patch or post.
    type: str
  request_body:
    description:
      - The request body to patch or post.
    type: dict
  validate_certs:
    version_added: 10.6.0
  ca_path:
    version_added: 10.6.0
  ciphers:
    version_added: 10.6.0

author: "Yuyan Pan (@panyy3)"
"""

EXAMPLES = r"""
- name: Insert Virtual Media
  community.general.xcc_redfish_command:
    category: Manager
    command: VirtualMediaInsert
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    virtual_media:
      image_url: "http://example.com/images/SomeLinux-current.iso"
      media_types:
        - CD
        - DVD
    resource_id: "1"

- name: Eject Virtual Media
  community.general.xcc_redfish_command:
    category: Manager
    command: VirtualMediaEject
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    virtual_media:
      image_url: "http://example.com/images/SomeLinux-current.iso"
    resource_id: "1"

- name: Eject all Virtual Media
  community.general.xcc_redfish_command:
    category: Manager
    command: VirtualMediaEject
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    resource_id: "1"

- name: Get ComputeSystem Oem property SystemStatus via GetResource command
  community.general.xcc_redfish_command:
    category: Raw
    command: GetResource
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    resource_uri: "/redfish/v1/Systems/1"
  register: result
- ansible.builtin.debug:
    msg: "{{ result.redfish_facts.data.Oem.Lenovo.SystemStatus }}"

- name: Get Oem DNS setting via GetResource command
  community.general.xcc_redfish_command:
    category: Raw
    command: GetResource
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    resource_uri: "/redfish/v1/Managers/1/NetworkProtocol/Oem/Lenovo/DNS"
  register: result

- name: Print fetched information
  ansible.builtin.debug:
    msg: "{{ result.redfish_facts.data }}"

- name: Get Lenovo FoD key collection resource via GetCollectionResource command
  community.general.xcc_redfish_command:
    category: Raw
    command: GetCollectionResource
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    resource_uri: "/redfish/v1/Managers/1/Oem/Lenovo/FoD/Keys"
  register: result

- name: Print fetched information
  ansible.builtin.debug:
    msg: "{{ result.redfish_facts.data_list }}"

- name: Update ComputeSystem property AssetTag via PatchResource command
  community.general.xcc_redfish_command:
    category: Raw
    command: PatchResource
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    resource_uri: "/redfish/v1/Systems/1"
    request_body:
      AssetTag: "new_asset_tag"

- name: Perform BootToBIOSSetup action via PostResource command
  community.general.xcc_redfish_command:
    category: Raw
    command: PostResource
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    resource_uri: "/redfish/v1/Systems/1/Actions/Oem/LenovoComputerSystem.BootToBIOSSetup"
    request_body: {}

- name: Perform SecureBoot.ResetKeys action via PostResource command
  community.general.xcc_redfish_command:
    category: Raw
    command: PostResource
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
    resource_uri: "/redfish/v1/Systems/1/SecureBoot/Actions/SecureBoot.ResetKeys"
    request_body:
      ResetKeysType: DeleteAllKeys

- name: Create session
  community.general.redfish_command:
    category: Sessions
    command: CreateSession
    baseuri: "{{ baseuri }}"
    username: "{{ username }}"
    password: "{{ password }}"
  register: result

- name: Update Manager DateTimeLocalOffset property using security token for auth
  community.general.xcc_redfish_command:
    category: Raw
    command: PatchResource
    baseuri: "{{ baseuri }}"
    auth_token: "{{ result.session.token }}"
    resource_uri: "/redfish/v1/Managers/1"
    request_body:
      DateTimeLocalOffset: "+08:00"

- name: Delete session using security token created by CreateSesssion above
  community.general.redfish_command:
    category: Sessions
    command: DeleteSession
    baseuri: "{{ baseuri }}"
    auth_token: "{{ result.session.token }}"
    session_uri: "{{ result.session.uri }}"
"""

RETURN = r"""
msg:
  description: A message related to the performed action(s).
  returned: when failure or action/update success
  type: str
  sample: "Action was successful"
redfish_facts:
  description: Resource content.
  returned: when command == GetResource or command == GetCollectionResource
  type: dict
  sample:
    {
      "redfish_facts": {
        "data": {
          "@odata.etag": "\"3179bf00d69f25a8b3c\"",
          "@odata.id": "/redfish/v1/Managers/1/NetworkProtocol/Oem/Lenovo/DNS",
          "@odata.type": "#LenovoDNS.v1_0_0.LenovoDNS",
          "DDNS": [
            {
              "DDNSEnable": true,
              "DomainName": "",
              "DomainNameSource": "DHCP"
            }
          ],
          "DNSEnable": true,
          "Description": "This resource is used to represent a DNS resource for a Redfish implementation.",
          "IPv4Address1": "10.103.62.178",
          "IPv4Address2": "0.0.0.0",
          "IPv4Address3": "0.0.0.0",
          "IPv6Address1": "::",
          "IPv6Address2": "::",
          "IPv6Address3": "::",
          "Id": "LenovoDNS",
          "PreferredAddresstype": "IPv4"
        },
        "ret": true
      }
    }
"""

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.general.plugins.module_utils.redfish_utils import (
    RedfishUtils,
    REDFISH_COMMON_ARGUMENT_SPEC,
)


class XCCRedfishUtils(RedfishUtils):
    @staticmethod
    def _find_empty_virt_media_slot(resources, media_types, media_match_strict=True):
        for uri, data in resources.items():
            # check MediaTypes
            if "MediaTypes" in data and media_types:
                if not set(media_types).intersection(set(data["MediaTypes"])):
                    continue
            else:
                if media_match_strict:
                    continue
            if "RDOC" in uri:
                continue
            if "Remote" in uri:
                continue
            # if ejected, 'Inserted' should be False and 'ImageName' cleared
            if not data.get("Inserted", False) and not data.get("ImageName"):
                return uri, data
        return None, None

    def virtual_media_eject_one(self, image_url):
        # read the VirtualMedia resources from systems
        response = self.get_request(self.root_uri + self.systems_uri)
        if response["ret"] is False:
            return response
        data = response["data"]
        if "VirtualMedia" not in data:
            # read the VirtualMedia resources from manager
            response = self.get_request(self.root_uri + self.manager_uri)
            if response["ret"] is False:
                return response
            data = response["data"]
            if "VirtualMedia" not in data:
                return {"ret": False, "msg": "VirtualMedia resource not found"}
        virt_media_uri = data["VirtualMedia"]["@odata.id"]
        response = self.get_request(self.root_uri + virt_media_uri)
        if response["ret"] is False:
            return response
        data = response["data"]
        virt_media_list = []
        for member in data["Members"]:
            virt_media_list.append(member["@odata.id"])
        resources, headers = self._read_virt_media_resources(virt_media_list)

        # find the VirtualMedia resource to eject
        uri, data, eject = self._find_virt_media_to_eject(resources, image_url)
        if uri and eject:
            if "Actions" not in data or "#VirtualMedia.EjectMedia" not in data["Actions"]:
                # try to eject via PATCH if no EjectMedia action found
                h = headers[uri]
                if "allow" in h:
                    methods = [m.strip() for m in h.get("allow").split(",")]
                    if "PATCH" not in methods:
                        # if Allow header present and PATCH missing, return error
                        return {"ret": False, "msg": "#VirtualMedia.EjectMedia action not found and PATCH not allowed"}
                return self.virtual_media_eject_via_patch(uri)
            else:
                # POST to the EjectMedia Action
                action = data["Actions"]["#VirtualMedia.EjectMedia"]
                if "target" not in action:
                    return {"ret": False, "msg": "target URI property missing from Action #VirtualMedia.EjectMedia"}
                action_uri = action["target"]
                # empty payload for Eject action
                payload = {}
                # POST to action
                response = self.post_request(self.root_uri + action_uri, payload)
                if response["ret"] is False:
                    return response
                return {"ret": True, "changed": True, "msg": "VirtualMedia ejected"}
        elif uri and not eject:
            # already ejected: return success but changed=False
            return {"ret": True, "changed": False, "msg": f"VirtualMedia image '{image_url}' already ejected"}
        else:
            # return failure (no resources matching image_url found)
            return {
                "ret": False,
                "changed": False,
                "msg": f"No VirtualMedia resource found with image '{image_url}' inserted",
            }

    def virtual_media_eject(self, options):
        if options:
            image_url = options.get("image_url")
            if image_url:  # eject specified one media
                return self.virtual_media_eject_one(image_url)

        # eject all inserted media when no image_url specified
        # read the VirtualMedia resources from systems
        response = self.get_request(self.root_uri + self.systems_uri)
        if response["ret"] is False:
            return response
        data = response["data"]
        if "VirtualMedia" not in data:
            # read the VirtualMedia resources from manager
            response = self.get_request(self.root_uri + self.manager_uri)
            if response["ret"] is False:
                return response
            data = response["data"]
            if "VirtualMedia" not in data:
                return {"ret": False, "msg": "VirtualMedia resource not found"}
        # read all the VirtualMedia resources
        virt_media_uri = data["VirtualMedia"]["@odata.id"]
        response = self.get_request(self.root_uri + virt_media_uri)
        if response["ret"] is False:
            return response
        data = response["data"]
        virt_media_list = []
        for member in data["Members"]:
            virt_media_list.append(member["@odata.id"])
        resources, headers = self._read_virt_media_resources(virt_media_list)

        # eject all inserted media one by one
        ejected_media_list = []
        for data in resources.values():
            if data.get("Image") and data.get("Inserted", True):
                returndict = self.virtual_media_eject_one(data.get("Image"))
                if not returndict["ret"]:
                    return returndict
                ejected_media_list.append(data.get("Image"))

        if len(ejected_media_list) == 0:
            # no media inserted: return success but changed=False
            return {"ret": True, "changed": False, "msg": "No VirtualMedia image inserted"}
        else:
            return {"ret": True, "changed": True, "msg": f"VirtualMedia {ejected_media_list!s} ejected"}

    def virtual_media_insert(self, options):
        param_map = {
            "Inserted": "inserted",
            "WriteProtected": "write_protected",
            "UserName": "username",
            "Password": "password",
            "TransferProtocolType": "transfer_protocol_type",
            "TransferMethod": "transfer_method",
        }
        image_url = options.get("image_url")
        if not image_url:
            return {"ret": False, "msg": "image_url option required for VirtualMediaInsert"}
        media_types = options.get("media_types")

        # read the VirtualMedia resources from systems
        response = self.get_request(self.root_uri + self.systems_uri)
        if response["ret"] is False:
            return response
        data = response["data"]
        if "VirtualMedia" not in data:
            # read the VirtualMedia resources from manager
            response = self.get_request(self.root_uri + self.manager_uri)
            if response["ret"] is False:
                return response
            data = response["data"]
            if "VirtualMedia" not in data:
                return {"ret": False, "msg": "VirtualMedia resource not found"}
        virt_media_uri = data["VirtualMedia"]["@odata.id"]
        response = self.get_request(self.root_uri + virt_media_uri)
        if response["ret"] is False:
            return response
        data = response["data"]
        virt_media_list = []
        for member in data["Members"]:
            virt_media_list.append(member["@odata.id"])
        resources, headers = self._read_virt_media_resources(virt_media_list)

        # see if image already inserted; if so, nothing to do
        if self._virt_media_image_inserted(resources, image_url):
            return {"ret": True, "changed": False, "msg": f"VirtualMedia '{image_url}' already inserted"}

        # find an empty slot to insert the media
        # try first with strict media_type matching
        uri, data = self._find_empty_virt_media_slot(resources, media_types, media_match_strict=True)
        if not uri:
            # if not found, try without strict media_type matching
            uri, data = self._find_empty_virt_media_slot(resources, media_types, media_match_strict=False)
        if not uri:
            return {
                "ret": False,
                "msg": f"Unable to find an available VirtualMedia resource {f'supporting {media_types}' if media_types else ''}",
            }

        # confirm InsertMedia action found
        if "Actions" not in data or "#VirtualMedia.InsertMedia" not in data["Actions"]:
            # try to insert via PATCH if no InsertMedia action found
            h = headers[uri]
            if "allow" in h:
                methods = [m.strip() for m in h.get("allow").split(",")]
                if "PATCH" not in methods:
                    # if Allow header present and PATCH missing, return error
                    return {"ret": False, "msg": "#VirtualMedia.InsertMedia action not found and PATCH not allowed"}
            return self.virtual_media_insert_via_patch(options, param_map, uri, data)

        # get the action property
        action = data["Actions"]["#VirtualMedia.InsertMedia"]
        if "target" not in action:
            return {"ret": False, "msg": "target URI missing from Action #VirtualMedia.InsertMedia"}
        action_uri = action["target"]
        # get ActionInfo or AllowableValues
        ai = self._get_all_action_info_values(action)
        # construct payload
        payload = self._insert_virt_media_payload(options, param_map, data, ai)
        # POST to action
        response = self.post_request(self.root_uri + action_uri, payload)
        if response["ret"] is False:
            return response
        return {"ret": True, "changed": True, "msg": "VirtualMedia inserted"}

    def raw_get_resource(self, resource_uri):
        if resource_uri is None:
            return {"ret": False, "msg": "resource_uri is missing"}
        response = self.get_request(self.root_uri + resource_uri)
        if response["ret"] is False:
            return response
        data = response["data"]
        return {"ret": True, "data": data}

    def raw_get_collection_resource(self, resource_uri):
        if resource_uri is None:
            return {"ret": False, "msg": "resource_uri is missing"}
        response = self.get_request(self.root_uri + resource_uri)
        if response["ret"] is False:
            return response
        if "Members" not in response["data"]:
            return {"ret": False, "msg": "Specified resource_uri doesn't have Members property"}
        member_list = [i["@odata.id"] for i in response["data"].get("Members", [])]

        # get member resource one by one
        data_list = []
        for member_uri in member_list:
            uri = self.root_uri + member_uri
            response = self.get_request(uri)
            if response["ret"] is False:
                return response
            data = response["data"]
            data_list.append(data)

        return {"ret": True, "data_list": data_list}

    def raw_patch_resource(self, resource_uri, request_body):
        if resource_uri is None:
            return {"ret": False, "msg": "resource_uri is missing"}
        if request_body is None:
            return {"ret": False, "msg": "request_body is missing"}
        # check whether resource_uri existing or not
        response = self.get_request(self.root_uri + resource_uri)
        if response["ret"] is False:
            return response
        original_etag = response["data"]["@odata.etag"]

        # check validity of keys in request_body
        data = response["data"]
        for key in request_body.keys():
            if key not in data:
                return {"ret": False, "msg": f"Key {key} not found. Supported key list: {list(data)}"}

        # perform patch
        response = self.patch_request(self.root_uri + resource_uri, request_body)
        if response["ret"] is False:
            return response

        # check whether changed or not
        current_etag = ""
        if "data" in response and "@odata.etag" in response["data"]:
            current_etag = response["data"]["@odata.etag"]
        if current_etag != original_etag:
            return {"ret": True, "changed": True}
        else:
            return {"ret": True, "changed": False}

    def raw_post_resource(self, resource_uri, request_body):
        if resource_uri is None:
            return {"ret": False, "msg": "resource_uri is missing"}
        resource_uri_has_actions = True
        if "/Actions/" not in resource_uri:
            resource_uri_has_actions = False
        if request_body is None:
            return {"ret": False, "msg": "request_body is missing"}
        # get action base uri data for further checking
        action_base_uri = resource_uri.split("/Actions/")[0]
        response = self.get_request(self.root_uri + action_base_uri)
        if response["ret"] is False:
            return response
        if "Actions" not in response["data"]:
            if resource_uri_has_actions:
                return {"ret": False, "msg": f"Actions property not found in {action_base_uri}"}
            else:
                response["data"]["Actions"] = {}

        # check resouce_uri with target uri found in action base uri data
        action_found = False
        action_info_uri = None
        action_target_uri_list = []
        for key in response["data"]["Actions"].keys():
            if action_found:
                break
            if not key.startswith("#"):
                continue
            if "target" in response["data"]["Actions"][key]:
                if resource_uri == response["data"]["Actions"][key]["target"]:
                    action_found = True
                    if "@Redfish.ActionInfo" in response["data"]["Actions"][key]:
                        action_info_uri = response["data"]["Actions"][key]["@Redfish.ActionInfo"]
                else:
                    action_target_uri_list.append(response["data"]["Actions"][key]["target"])
        if not action_found and "Oem" in response["data"]["Actions"]:
            for key in response["data"]["Actions"]["Oem"].keys():
                if action_found:
                    break
                if not key.startswith("#"):
                    continue
                if "target" in response["data"]["Actions"]["Oem"][key]:
                    if resource_uri == response["data"]["Actions"]["Oem"][key]["target"]:
                        action_found = True
                        if "@Redfish.ActionInfo" in response["data"]["Actions"]["Oem"][key]:
                            action_info_uri = response["data"]["Actions"]["Oem"][key]["@Redfish.ActionInfo"]
                    else:
                        action_target_uri_list.append(response["data"]["Actions"]["Oem"][key]["target"])

        if not action_found and resource_uri_has_actions:
            return {
                "ret": False,
                "msg": (
                    f"Specified resource_uri is not a supported action target uri, please specify a supported target uri instead. "
                    f"Supported uri: {action_target_uri_list}"
                ),
            }

        # check request_body with parameter name defined by @Redfish.ActionInfo
        if action_info_uri is not None:
            response = self.get_request(self.root_uri + action_info_uri)
            if response["ret"] is False:
                return response
            for key in request_body.keys():
                key_found = False
                for para in response["data"]["Parameters"]:
                    if key == para["Name"]:
                        key_found = True
                        break
                if not key_found:
                    return {
                        "ret": False,
                        "msg": (
                            f"Invalid property {key} found in request_body. "
                            f"Please refer to @Redfish.ActionInfo Parameters: {response['data']['Parameters']}"
                        ),
                    }

        # perform post
        response = self.post_request(self.root_uri + resource_uri, request_body)
        if response["ret"] is False:
            return response
        return {"ret": True, "changed": True}


# More will be added as module features are expanded
CATEGORY_COMMANDS_ALL = {
    "Manager": ["VirtualMediaInsert", "VirtualMediaEject"],
    "Raw": ["GetResource", "GetCollectionResource", "PatchResource", "PostResource"],
}


def main():
    result = {}
    argument_spec = dict(
        category=dict(required=True),
        command=dict(required=True, type="list", elements="str"),
        baseuri=dict(required=True),
        username=dict(),
        password=dict(no_log=True),
        auth_token=dict(no_log=True),
        timeout=dict(type="int", default=10),
        resource_id=dict(),
        virtual_media=dict(
            type="dict",
            options=dict(
                media_types=dict(type="list", elements="str", default=[]),
                image_url=dict(),
                inserted=dict(type="bool", default=True),
                write_protected=dict(type="bool", default=True),
                username=dict(),
                password=dict(no_log=True),
                transfer_protocol_type=dict(),
                transfer_method=dict(),
            ),
        ),
        resource_uri=dict(),
        request_body=dict(
            type="dict",
        ),
    )
    argument_spec.update(REDFISH_COMMON_ARGUMENT_SPEC)
    module = AnsibleModule(
        argument_spec,
        required_together=[
            ("username", "password"),
        ],
        required_one_of=[
            ("username", "auth_token"),
        ],
        mutually_exclusive=[
            ("username", "auth_token"),
        ],
        supports_check_mode=False,
    )

    category = module.params["category"]
    command_list = module.params["command"]

    # admin credentials used for authentication
    creds = {"user": module.params["username"], "pswd": module.params["password"], "token": module.params["auth_token"]}

    # timeout
    timeout = module.params["timeout"]

    # System, Manager or Chassis ID to modify
    resource_id = module.params["resource_id"]

    # VirtualMedia options
    virtual_media = module.params["virtual_media"]

    # resource_uri
    resource_uri = module.params["resource_uri"]

    # request_body
    request_body = module.params["request_body"]

    # Build root URI
    root_uri = f"https://{module.params['baseuri']}"
    rf_utils = XCCRedfishUtils(creds, root_uri, timeout, module, resource_id=resource_id, data_modification=True)

    # Check that Category is valid
    if category not in CATEGORY_COMMANDS_ALL:
        module.fail_json(msg=f"Invalid Category '{category}'. Valid Categories = {list(CATEGORY_COMMANDS_ALL.keys())}")

    # Check that all commands are valid
    for cmd in command_list:
        # Fail if even one command given is invalid
        if cmd not in CATEGORY_COMMANDS_ALL[category]:
            module.fail_json(msg=f"Invalid Command '{cmd}'. Valid Commands = {CATEGORY_COMMANDS_ALL[category]}")

    # Organize by Categories / Commands
    if category == "Manager":
        # For virtual media resource locates on Systems service
        result = rf_utils._find_systems_resource()
        if result["ret"] is False:
            module.fail_json(msg=result["msg"])
        # For virtual media resource locates on Managers service
        result = rf_utils._find_managers_resource()
        if result["ret"] is False:
            module.fail_json(msg=result["msg"])

        for command in command_list:
            if command == "VirtualMediaInsert":
                result = rf_utils.virtual_media_insert(virtual_media)
            elif command == "VirtualMediaEject":
                result = rf_utils.virtual_media_eject(virtual_media)
    elif category == "Raw":
        for command in command_list:
            if command == "GetResource":
                result = rf_utils.raw_get_resource(resource_uri)
            elif command == "GetCollectionResource":
                result = rf_utils.raw_get_collection_resource(resource_uri)
            elif command == "PatchResource":
                result = rf_utils.raw_patch_resource(resource_uri, request_body)
            elif command == "PostResource":
                result = rf_utils.raw_post_resource(resource_uri, request_body)

    # Return data back or fail with proper message
    if result["ret"] is True:
        if command == "GetResource" or command == "GetCollectionResource":
            module.exit_json(redfish_facts=result)
        else:
            changed = result.get("changed", True)
            msg = result.get("msg", "Action was successful")
            module.exit_json(changed=changed, msg=msg)
    else:
        module.fail_json(msg=to_native(result["msg"]))


if __name__ == "__main__":
    main()
